﻿using RockBus.Configuration.Interfaces;
using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using RockBus.Messages.Interfaces;
using RockBus.Pipeline.Interfaces;
using RockBus.PipelineComponent.Interfaces;
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.ServiceModel.Channels;
using System.Configuration;
using RockBus.MessageLogging.Interfaces;

namespace RockBus.Pipeline
{
    public class PipelineDictionaries
    {
        public PipelineDictionaries(ILogger logger, IReadOnlyConfiguration configuration, IMessageLogger messageLogger)
        {
            this.Logger = logger;
            this.Configuration = configuration;
            this.MessageLogger = messageLogger;

            var dict = this.Configuration.SubscriberConfiguration.CreatePipelineInfoDictionary();
            foreach (var kvp in this.Configuration.PublisherConfiguration.CreatePipelineInfoDictionary())
            {
                dict.Add(kvp.Key, kvp.Value);
            }

            this.PipelineInfoDictionary = dict;
            this.PipelineDictionary = new ConcurrentDictionary<Tuple<string, string>, IPipeline>();
        }

        private ILogger Logger { get; set; }

        private IReadOnlyConfiguration Configuration { get; set; }

        private IMessageLogger MessageLogger { get; set; }

        public Dictionary<Tuple<string, string>, PipelineInfo> PipelineInfoDictionary;

        public ConcurrentDictionary<Tuple<string, string>, IPipeline> PipelineDictionary;

        public IPipeline GetOrAddPipeline(string parentId, string pipelineId)
        {
            var key = new Tuple<string, string>(parentId, pipelineId);
            return this.PipelineDictionary.GetOrAdd(key, (id) => { return this.CreatePipeline(key); });
        }

        private IPipeline CreatePipeline(Tuple<string, string> key)
        {
            if (!this.PipelineInfoDictionary.ContainsKey(key))
            {
                return null;
            }

            var pipelineInfo = this.PipelineInfoDictionary[key];
            var pipelineComponents = new List<IPipelineComponent>();
            foreach (var pci in pipelineInfo.PipelineComponents)
            {
                var type = GetFromCurrentContext(pci.TypeName);
                if (type == null)
                {
                    type = GetFromDefaultContext(pci.TypeName);
                }
                if (type == null)
                {
                    type = GetFromLoadFromContext(this.Configuration.InternalConfiguration.CustomAssemblyFolder, pci.TypeName);
                }

                if (type == null)
                {
                    throw new ConfigurationErrorsException(string.Format("Unable to load pipeline component with typename: {0}", pci.TypeName));
                }

                var pcc = new PipelineComponentConfiguration(pci.Id, pci.Active, pci.SoapActionFilter, pci.Configuration, this.Configuration);
                var pc = Activator.CreateInstance(type, pcc) as IPipelineComponent;
                if (pc == null)
                {
                    throw new ArgumentException("Could not instantiate pipeline component for the specified type.", pci.TypeName);
                }
                pipelineComponents.Add(pc);
            }

            var pipeline = new Pipeline(this.Logger, this.MessageLogger, key.Item2, pipelineComponents);
            return pipeline;
        }

        private Type GetFromCurrentContext(string assemblyQualifiedTypeName)
        {
            try
            {
                return Type.GetType(assemblyQualifiedTypeName);
            }
            catch
            {
                return null;
            }
        }

        private Type GetFromDefaultContext(string assemblyQualifiedTypeName)
        {
            try
            {
                var assemblyName = assemblyQualifiedTypeName.Substring(assemblyQualifiedTypeName.IndexOf(',') + 1).Trim(' ');
                var assembly = Assembly.Load(assemblyName);

                var typeName = assemblyQualifiedTypeName.Substring(0, assemblyQualifiedTypeName.IndexOf(','));
                var type = assembly.GetType(typeName, true);

                return type;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Returns the Type of from the "LoadFrom" context
        /// </summary>
        /// <param name="path"></param>
        /// <param name="assemblyName"></param>
        /// <param name="typeName"></param>
        private Type GetFromLoadFromContext(string loadFromDir, string assemblyQualifiedTypeName)
        {
            var typeName = assemblyQualifiedTypeName.Substring(0, assemblyQualifiedTypeName.IndexOf(','));

            foreach (var fileName in Directory.GetFiles(loadFromDir, "*.dll"))
            {
                try
                {
                    var assembly = Assembly.LoadFrom(fileName);
                    var type = assembly.GetType(typeName);
                    if (type != null)
                    {
                        return type;
                    }
                }
                catch
                {
                    // Intentionally left blank
                }
            }
            return null;
        }
    }

    public class PipelineFactory : IPipelineFactory
    {
        public PipelineFactory(ILogger logger, IReadOnlyConfiguration configuration, IMessageLogger messageLogger, IMessageFactory messageFactory)
        {
            this.Logger = logger;
            this.Configuration = configuration;
            this.MessageLogger = messageLogger;
            this.MessageFactory = messageFactory;
        }

        private ILogger Logger { get; set; }

        private IReadOnlyConfiguration Configuration { get; set; }

        private IMessageLogger MessageLogger { get; set; }

        private IMessageFactory MessageFactory { get; set; }

        private PipelineDictionaries PipelineDictionaries;

        #region IPipelineFactory

        public void Reinitialize(SubscriberConfiguration subscriberConfiguration, PublisherConfiguration publisherConfiguration)
        {
            this.PipelineDictionaries = new PipelineDictionaries(this.Logger, this.Configuration, this.MessageLogger);
        }

        public IPipelineContext CreatePipelineContext(Message message)
        {
            return new PipelineContext(this.Logger, this.MessageFactory, this.Configuration);
        }

        public IPipeline CreateInboundRequestPipeline(ReceivePortDetail receivePortDetail)
        {
            if ((receivePortDetail == null) || (string.IsNullOrEmpty(receivePortDetail.RequestPipelineId)))
            {
                return null;
            }

            return this.PipelineDictionaries.GetOrAddPipeline(receivePortDetail.ParentId, receivePortDetail.RequestPipelineId);
        }

        public IPipeline CreateOutboundRequestPipeline(SubscriptionDetail subscriptionDetail)
        {
            if ((subscriptionDetail == null) || (string.IsNullOrEmpty(subscriptionDetail.RequestPipelineId)))
            {
                return null;
            }

            return this.PipelineDictionaries.GetOrAddPipeline(subscriptionDetail.ParentId, subscriptionDetail.RequestPipelineId);
        }

        public IPipeline CreateInboundResponsePipeline(SubscriptionDetail subscriptionDetail)
        {
            if ((subscriptionDetail == null) || (string.IsNullOrEmpty(subscriptionDetail.ResponsePipelineId)))
            {
                return null;
            }

            return this.PipelineDictionaries.GetOrAddPipeline(subscriptionDetail.ParentId, subscriptionDetail.ResponsePipelineId);
        }

        public IPipeline CreateOutboundResponsePipeline(ReceivePortDetail receivePortDetail)
        {
            if ((receivePortDetail == null) || (string.IsNullOrEmpty(receivePortDetail.ResponsePipelineId)))
            {
                return null;
            }

            return this.PipelineDictionaries.GetOrAddPipeline(receivePortDetail.ParentId, receivePortDetail.ResponsePipelineId);
        }

        #endregion IPipelineFactory
    }
}